package cn.swenty.backstage.framework.database.plugin;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;

import cn.swenty.backstage.framework.database.entity.Entity;
import cn.swenty.backstage.framework.database.mybatis.enums.DATABASE;
import cn.swenty.backstage.framework.database.mybatis.sql.SqlHelp;
import cn.swenty.backstage.framework.util.ReflectHelper;

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class PagePlugin implements Interceptor{

	
	
	private String dialect;

	public String getDialect() {
		return dialect;
	}

	public void setDialect(String dialect) {
		this.dialect = dialect;
	}


	private static final Pattern PATTERN_SQL_BLANK = Pattern.compile("\\s+");

	private static final String FIELD_DELEGATE = "delegate";

	private static final String FIELD_MAPPEDSTATEMENT = "mappedStatement";

	private static final String FIELD_SQL = "sql";

	private static final String BLANK = " ";

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
	
			Connection connection = (Connection) invocation.getArgs()[0];
			RoutingStatementHandler statementHandler = (RoutingStatementHandler) invocation.getTarget();
			StatementHandler handler = (StatementHandler) ReflectHelper.getValueByFieldName(statementHandler, FIELD_DELEGATE);
			Object obj = handler.getBoundSql().getParameterObject();
			if (obj instanceof Entity) {
				// 如果有分页参数，则调用
				if (((Entity) obj).get(Entity.PAGE_VIEW) != null) {
					PageView pageView = (PageView) ((Entity) obj).get(Entity.PAGE_VIEW);
					MappedStatement mappedStatement = (MappedStatement)ReflectHelper.getValueByFieldName(handler, FIELD_MAPPEDSTATEMENT);
					BoundSql boundSql = handler.getBoundSql();

					// replace all blank
					String targetSql = replaceSqlBlank(boundSql.getSql());

					// paging
					getTotalAndSetInPagingBounds(targetSql, boundSql, pageView, mappedStatement, connection);
					String pagingSql = SqlHelp.getSelectPagingSql(targetSql,DATABASE.get(dialect),pageView.getOrderby(), pageView.getFirstResult(),pageView.getPageSize());
					ReflectHelper.setValueByFieldName(boundSql, FIELD_SQL, pagingSql);

				}
			}
		
		return invocation.proceed();
	}

	private void getTotalAndSetInPagingBounds(String targetSql, BoundSql boundSql, PageView pageView,
			MappedStatement mappedStatement, Connection connection) throws SQLException {
		String totalSql = SqlHelp.getSelectTotalSql(targetSql,DATABASE.get(dialect));
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		Object parameterObject = boundSql.getParameterObject();
		BoundSql totalBoundSql = new BoundSql(mappedStatement.getConfiguration(), totalSql, parameterMappings,
				parameterObject);
		ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject,
				totalBoundSql);

		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			pstmt = connection.prepareStatement(totalSql);
			parameterHandler.setParameters(pstmt);
			
			rs = pstmt.executeQuery();
			if (rs.next()) {
				int totalRecord = rs.getInt(1);
				pageView.setRowCount(totalRecord);
			}
		} finally {
			if (rs != null) {
				rs.close();
			}
			if (pstmt != null) {
				pstmt.close();
			}
		}
	}

	private String replaceSqlBlank(String originalSql) {
		Matcher matcher = PATTERN_SQL_BLANK.matcher(originalSql);
		return matcher.replaceAll(BLANK);
	}

	@Override
	public Object plugin(Object target) {
		if(target  instanceof RoutingStatementHandler){
			StatementHandler handler = (StatementHandler) ReflectHelper.getValueByFieldName(target, FIELD_DELEGATE);
			BoundSql boundSql = handler.getBoundSql();
			Object parameterObject = boundSql.getParameterObject();
			if(parameterObject instanceof Entity && ((Entity) parameterObject).get(Entity.PAGE_VIEW) != null){
				
				return Plugin.wrap(target, this);
			}
		}
		return target;
	}


	@Override
	public void setProperties(Properties properties) {
		dialect = properties.getProperty("dialect");
	}

}